#include "armory.h"
#include "consumable.h"
#include "defines.h"
#include "item.h"
#include "manager.h"
#include "monster.h"
#include "tile.h"

using std::string;

Monster::Monster( int x, int y, stats_t attrs, skills_t skls, string name, char symbol ) : MapObject( -1, -1, name, symbol )
{
	health = 30;
	stats = attrs;
	skills = skls;
	dead = false;
	armor = nullptr;
	weapon = nullptr;
	defaultColor = color;
	type_.monster = 1;
	layer = 2;
	setPos( x, y );
	manager.getMMrs().push_back( this );
}

Monster::Monster( const monster_t & baseMonster, int x, int y ) : MapObject( -1, -1, baseMonster.name, baseMonster.symbol )
{
	health = 30;
	stats = baseMonster.attributes;
	skills = baseMonster.skills;
	dead = false;
	armor = nullptr;
	weapon = nullptr;
	defaultColor = color = baseMonster.color;
	type_.monster = 1;
	layer = 2;
	setPos( x, y );
	manager.getMMrs().push_back( this );
}

Monster::~Monster()
{
	setPos( -1, -1 );
	manager.getMMrs().remove( this );
}

int Monster::setPos( int x, int y )
{
	Tile * prevLoc = tileAt( this );
	if( MapObject::setPos( x, y ) )
	{
		if( prevLoc )
			prevLoc->removeFromContents( this );
		Tile * newLoc = tileAt( this );
		if( newLoc )
			newLoc->addToContents( this );
		return 1;
	}
	return 0;
}

int Monster::setMapPos( map * m, int x, int y )
{
	Tile * prevLoc = mapTileAt( m, this );
	if( MapObject::setMapPos( m, x, y ) )
	{
		if( prevLoc )
			prevLoc->removeFromContents( this );
		Tile * newLoc = mapTileAt( m, this );
		if( newLoc )
			newLoc->addToContents( this );
		if( map_ != m )
		{
			if( map_ )
				m->mapMonsters.remove( this );
			m->mapMonsters.push_back( this );
		}
		return 1;
	}
	return 0;
}

bool Monster::move( int direction )
{
	if( isDead() )
		return false;
	int xAdj;
	int yAdj;
	getAdj( direction, xAdj, yAdj );
	Tile * tileToStep = tileAt( this->getX() + xAdj, this->getY() + yAdj );
	if( tileToStep != nullptr )
	{
		if( tileToStep->monsterContents.empty() )
			return MapObject::move( direction );
		else
		{
			// check if there's a live monster on the tile
			for( auto iter = tileToStep->monsterContents.begin();
				iter != tileToStep->monsterContents.end(); iter++ )
			{
				Monster * mon = ( *iter );
				if( !mon->isDead() )
					return false;
			}
			return MapObject::move( direction );
		}
	}
	else
		return false;
}

bool Monster::moveAttack( int direction )
{
	if( isDead() )
		return false;
	if( move( direction ) )
		return true;
	Monster * m = getLiveMonster( this, direction );
	if( m )
		return attack( m );
	else
		return false;
}

void Monster::pickUp( Item * item )
{
	if( !item || invFind( item ) )
		return; // error
	invAdd( item );
	manager.pickMsg( this, item );
}

// must return 0 when you don't drop shit
void Monster::drop( Item * item )
{
	if( item == nullptr )
		//log file here
		return;
	if( item == armor || item == weapon )
	{
		manager.addMsg( "Take it off first." );
		return;
	}
	invRemoveDrop( item );
	manager.dropMsg( this, item );
}

//Health and fighting related

int Monster::getHealth() const
{
	return health;
}

void Monster::setHealth( int hp )
{
	health = hp;
	if( health <= 0 )
		die();
	else if( isDead() && health > 0 )
		revive();
	//else
		//gib(); // lol
}

void Monster::adjustHealth( int hp )
{
	setHealth( health + hp );
}

void Monster::heal( int hp )
{
	if( hp > 0 )
	{
		this->setHealth( health + hp );
	}
}

void Monster::harm( int dam )
{
	if( dam > 0 )
	{
		this->setHealth( health - dam );
	}
}

bool Monster::isDead() const
{
	return dead;
}

bool Monster::die()
{
	if( isDead() )
		return false;
	dead = true;
	color = COLOR_BLOOD;
	layer = 0.5;
	manager.dieMsg( this );
	return true;
}

bool Monster::revive()
{
	if( !isDead() )
		return false;
	dead = false;
	color = COLOR_WHITE | A_BOLD;
	layer = 2;
	return true;
}

bool Monster::attack( Monster * enemy )
{
	if( enemy->isDead() )
		return false;
	int dodge = ( enemy->getAgl() - this->getAgl() ) * 3 + 7;
	dodge = ( dodge > 0 ? dodge : 0 );
	if( dodge && prob( dodge ) )
	{
		// enemy has evaded our attack
		manager.addVisMsg( enemy->name + " has dodged " + this->name + "'s attack!", this );
		return true;
	}
	// a chance to block
	// if didn't block, apply damage
	if( prob( (enemy->skills.defense - this->skills.weapon)*5 + 5 ) )
	{
		manager.addVisMsg( enemy->name + " has blocked " + this->name + "'s attack!", this );
		return true;
	}
	int damage = this->getDamage() - enemy->getProtection();
	manager.addVisMsg( enemy->name + " has been hit by " + this->name + "!", this );
	enemy->harm( ( damage < 1 ) ? 1 : damage );
	return true;
}

bool Monster::attackOld( Monster * enemy )
{
	if( enemy->isDead() )
		return false;// no corpse battering. For now.
	int success = -1;// miss by default
	int attackProb = d20() + this->skills.weapon + this->stats.agility;
	int defenseProb = 0/*shield skill if a shield is present*/ + enemy->stats.agility
		+ ( enemy->skills.weapon > enemy->skills.defense ? enemy->skills.weapon : enemy->skills.defense );
	if( attackProb > defenseProb )
	{
		int damageBonus = 15 + this->stats.strength
			+ ( this->weapon ? weapon->getDamage() : 0 )/*weapon damage*/;
		int damageResist = d20() + enemy->stats.endurance + enemy->getProtection()/*armor*/;
		int damage = damageBonus - damageResist;
		if( damage > 0 )
			success = damage;
		else
		{
			int failedDamage = damageBonus - 15 - enemy->stats.endurance - enemy->getProtection()/*armor*/;
			if( failedDamage > 0 )
				success = failedDamage;
			else
				success = 0;
		}
	}
	else
		;//miss effects
	manager.attackMsg( this, enemy, success );
	if( success > 0 )
		enemy->harm( success );
	return true;
}

void Monster::hitEffect( int force )
{
	manager.addMsg( this->name + " is hit!" );
	harm( force );
}

bool Monster::kick( Monster * mon )
{
	manager.addMsg( this->name + " kicks " + mon->name + "!" );
	mon->fly( getDir( this, mon ), 5 );
	return true;
}

int Monster::fly( int direction, int length )
{
	manager.addFlyMonster( this, length, direction );
	manager.flyMonsters();
	return 0;
}

inline int Monster::fly( MapObject * mobj, int length )
{
	return fly( getDir( this, mobj ), length );
}

//Attributes helpers

int Monster::getStr() const
{
	return this->stats.strength;
}

int Monster::getAgl() const
{
	return this->stats.agility;
}

int Monster::getInt() const
{
	return this->stats.inelligence;
}

int Monster::getEnd() const
{
	return this->stats.endurance;
}

//Armor/weapon

bool Monster::wear( Armor * arm )
{
	if( !arm )
		return false;//error
	if( armor )
	{
		manager.addMsg( "You are already wearing armor." );
		return false;
	}
	if( armor == arm )
	{
		manager.addMsg( "You are already wearing it." );
		return false;
	}
	else
		manager.addMsg( "You are wearing armor now." );
	armor = arm;
	color = armor->color;
	arm->setPos( -1, -1 );
	return true;
}

bool Monster::wear( MapObject * item)
{
	if( !item )
		return false;//error
	Armor * arm = isArmor( item );
	if( arm )
		return wear( arm );
	else
		manager.addMsg( "You can't wear that!" );
	return false;
}

bool Monster::takeOff( Armor * arm )
{
	if( !arm )
		return false;//error
	if( arm != armor )
	{
		manager.addMsg( "You aren't wearing this." );
		return false;
	}
	armor = nullptr;
	color = defaultColor;
	manager.addMsg( "You take off your armor." );
	return true;
}

bool Monster::takeOff( MapObject * item )
{
	if( !item )
		return false; // error
	Armor * arm = isArmor( item );
	if( arm )
		return takeOff( arm );
	else
		manager.addMsg( "You can't take off that!" );
	return false;
}

int Monster::getProtection() const
{
	if( armor )
		return armor->getProtection();
	return 0;
}

int Monster::getDamage() const
{
	if( weapon )
		return weapon->getDamage();
	return this->skills.brawl;
}

bool Monster::wield( MapObject * target )
{
	if( !target )
		return false; // error
	Weapon * w = isWeapon( target );
	if( w )
		return wield( w );
	manager.addMsg( "You can't wield that." );
	return false;
}

bool Monster::wield( Weapon * w )
{
	if( !weapon )
	{
		manager.addMsg( "You're now wielding " + w->name );
		weapon = w;
		return true;
	}
	manager.addMsg( "You're already wielding a weapon." );
	return false;
}

bool Monster::sheathe( MapObject * target )
{
	if( !target )
		return false; // error
	Weapon * w = isWeapon( target );
	if( w )
		return sheathe( w );
	manager.addMsg( "You can't sheathe that." );
	return false;
}

bool Monster::sheathe( Weapon * w )
{
	if( w == weapon )
	{
		manager.addMsg( "You sheathe your " + w->name );
		weapon = nullptr;
		return true;
	}
	manager.addMsg( "You can't sheathe something you're not wielding." );
	return false;
}

bool Monster::shoot( MapObject * target )
{
	if( !weapon )
		return false;
	if( !weapon->isFirearm() )
	{
		manager.addMsg( "You can't shoot with your " + weapon->name + "!" );
		return false;
	}
	manager.addMsg( "You shoot at " + target->name + "!" );
	MapObject * mobj = weapon->fire( target );
	return true;
}

Weapon * Monster::getWeapon() const
{
	return weapon;
}

Armor * Monster::getArmor() const
{
	return armor;
}

bool Monster::consume( MapObject * mobj )
{
	Consumable * cons = isConsumable( mobj );
	if( cons )
		return consume( cons );
	manager.addMsg( "Eating that would prove difficult." );
	return false;
}

bool Monster::consume( Consumable * cons )
{
	if( !cons )
		return false; // error
	cons->effect( this );
	invRemove( cons );
	delete cons;
	return true;
}

bool Monster::invRemove( Item * item )
{
	if( !item )
		return false; // error
	for( auto iter = items.begin(); iter != items.end(); iter++ )
	{
		if( *iter == item )
		{
			items.erase( iter );
			item->setOwner( nullptr );
			return true;
		}
	}
	return false;
}

bool Monster::invRemoveDrop( Item * item )
{
	if( !invRemove( item ) )
		return false;
	item->setPos( this );
	return true;
}

bool Monster::invFind( Item * item )
{
	if( !item )
		return false; // error
	for( auto iter = items.begin(); iter != items.end(); iter++ )
		if( *iter == item )
			return true;
	return false;
}

bool Monster::invAdd( Item * item )
{
	if( !item )
		return false; // error
	if( invFind( item ) )
		return false;
	item->setPos( -1, -1 );
	item->setOwner( this );
	items.push_back( item );
	return true;
}

bool Monster::openDoor( Tile * t )
{
	if( !t )
		return false; // error
	return t->open();
}

bool Monster::openDoor( int direction )
{
	return openDoor( tileAt( getX() + getXAdj( direction ),
		getY() + getYAdj( direction ) ) );
}

bool Monster::closeDoor( Tile * t )
{
	if( !t )
		return false; // error
	return t->close();
}

bool Monster::closeDoor( int direction )
{
	return closeDoor( tileAt( getX() + getXAdj( direction ),
		getY() + getYAdj( direction ) ) );
}

Monster::Monster()
{
	stats_t nullStat = { 0 };
	skills_t nullSkill = { 0 };
	stats = nullStat;
	skills = nullSkill;
	dead = false;
	armor = nullptr;
	weapon = nullptr;
	type_.monster = 1;
	layer = 2;
	health = 30;
}

void Monster::setMonster( map * m, int x, int y, stats_t attrs,
	skills_t skls, string name, char symbol )
{
	setMapObject( m, x, y, name, symbol );
	this->stats = attrs;
	this->skills = skls;
	defaultColor = color;
}